// Copyright 2009 (c) Tom van Dijck

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.ComponentModel;
using Equin.ApplicationFramework;

namespace Bonehead
{
    public partial class MainForm : Form
    {
        private Project mProject = null;

        public MainForm()
        {
            InitializeComponent();
            mProgress.Visible = false;
        }

        #region -- Event handlers ---------------------------------------------

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (mProject != null && mProject.IsProfiling)
            {
                e.Cancel = true;
            }
        }

        private void OnNewProject(object sender, EventArgs e)
        {
            if (mProject == null || !mProject.IsProfiling)
            {
                if (SaveFirst())
                {
                    OnCloseProject(this, EventArgs.Empty);
                    StartProfileDialog dlg = new StartProfileDialog();
                    if (dlg.ShowDialog(this) == DialogResult.OK)
                    {
                        mProject = new Project(dlg.Executable, dlg.Workingfolder, dlg.Arguments);
                        mProject.ProfileExited += new EventHandler(OnProcessExited);
                        OnSaveProject(this, EventArgs.Empty);
                        UpdateTree();
                    }
                }
            }
        }

        private void OnOpenProject(object sender, EventArgs e)
        {
            if (mProject == null || !mProject.IsProfiling)
            {
                if (SaveFirst())
                {
                    OnCloseProject(this, EventArgs.Empty);
                    if (mOpenFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        FileStream file = new FileStream(mOpenFileDialog.FileName, FileMode.Open);
                        XmlSerializer serializer = new XmlSerializer(typeof(Project));
                        mProject = serializer.Deserialize(file) as Project;
                        file.Close();

                        mProject.Name = mOpenFileDialog.FileName;
                        mProject.ProfileExited += new EventHandler(OnProcessExited);
                        UpdateTree();
                    }
                }
            }
        }

        private void OnCloseProject(object sender, EventArgs e)
        {
            if (mProject != null && !mProject.IsProfiling)
            {
                mProject.ProfileExited -= new EventHandler(OnProcessExited);
                mProject = null;
                UpdateTree();
            }
        }

        private void OnSaveProject(object sender, EventArgs e)
        {
            if (mProject != null && !mProject.IsProfiling)
            {
                if (String.IsNullOrEmpty(mProject.Name))
                {
                    if (mSaveFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        mProject.Name = mSaveFileDialog.FileName;
                        UpdateTree();
                    }
                    else
                    {
                        return;
                    }
                }

                FileStream file = new FileStream(mProject.Name, FileMode.Create);
                XmlSerializer serializer = new XmlSerializer(typeof(Project));
                serializer.Serialize(file, mProject);
                file.Close();
            }
        }

        private void OnSaveProjectAs(object sender, EventArgs e)
        {
            if (mProject != null && !mProject.IsProfiling)
            {
                if (mSaveFileDialog.ShowDialog() == DialogResult.OK)
                {
                    mProject.Name = mSaveFileDialog.FileName;
                    UpdateTree();
                }
                else
                {
                    return;
                }

                FileStream file = new FileStream(mProject.Name, FileMode.Create);
                XmlSerializer serializer = new XmlSerializer(typeof(Project));
                serializer.Serialize(file, mProject);
                file.Close();
            }
        }
              
        private void OnProcessExited(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new MethodInvoker(OnStopProfiling));
            }
            else
            {
                OnStopProfiling();
            }
        }

        private void OnLaunchProfile(object sender, EventArgs e)
        {
            if (mProject != null && !mProject.IsProfiling)
            {
                if (mProject.Start())
                {
                    mProgress.Visible = true;
                    UpdateTree();
                }
            }
        }

        private void OnStopProfile(object sender, EventArgs e)
        {
            if (mProject != null && mProject.IsProfiling)
            {
                mProject.Stop();
            }
        }

        private void mProjectTree_BeforeSelect(object sender, TreeViewCancelEventArgs e)
        {
            if (mProject != null && mProject.IsProfiling)
            {
                e.Cancel = true;
            }
        }

        private void mProjectTree_AfterSelect(object sender, TreeViewEventArgs e)
        {
            Profile profile = e.Node.Tag as Profile;
            if (profile != null)
            {
                ParsedProfile pp = new ParsedProfile(profile);
                SelectProfile(pp);
            }
        }

        private void mCallgraph_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            ParsedCallstack call = e.Node.Tag as ParsedCallstack;
            if (call != null && e.Node.Nodes.Count > 0 && e.Node.Nodes[0].Text == "dummy")
            {
                mCallgraph.BeginUpdate();
                AddMethodsToCallgraph(e.Node, call.Children);
                mCallgraph.EndUpdate();
            }
        }

        private void mCallgraph_AfterSelect(object sender, TreeViewEventArgs e)
        {
            mPropertyGrid.SelectedObject = e.Node.Tag;
        }

        private void mThreads_SelectedIndexChanged(object sender, EventArgs e)
        {
            ParsedThread th = mThreads.SelectedItem as ParsedThread;
            if (th != null)
            {
                List<ParsedMethod> list = new List<ParsedMethod>(th.Methods);
                mMethodList.DataSource = new MethodBindingList(list);
            }
        }

        private void mMethodList_SelectionChanged(object sender, EventArgs e)
        {
            if (mMethodList.SelectedRows.Count > 0)
            {
                DataGridViewRow row = mMethodList.SelectedRows[0];
                mPropertyGrid.SelectedObject = row.DataBoundItem;

                ParsedMethod method = row.DataBoundItem as ParsedMethod;
                if (method != null)
                {
                    List<CalleeInfo> callees = new List<CalleeInfo>(method.Callees);
                    mCallees.DataSource = new BindingListView<CalleeInfo>(callees);

                    List<CallerInfo> callers = new List<CallerInfo>(method.Callers);
                    mCallers.DataSource = new BindingListView<CallerInfo>(callers);
                }
            }
        }

        private void mCallers_CellContentDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (mCallers.SelectedRows.Count > 0)
            {
                DataGridViewRow row = mCallers.SelectedRows[0];
                BindingListView<CallerInfo> list = mCallers.DataSource as BindingListView<CallerInfo>;
                CallerInfo info = list[row.Index].Object;
                if (info != null)
                {
                    foreach (DataGridViewRow item in mMethodList.Rows)
                    {
                        ParsedMethod method = item.DataBoundItem as ParsedMethod;
                        if (Object.ReferenceEquals(method, info.Method))
                        {
                            item.Selected = true;
                            break;
                        }
                    }
                }
            }
        }

        private void mCallees_CellContentDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (mCallees.SelectedRows.Count > 0)
            {
                DataGridViewRow row = mCallees.SelectedRows[0];
                BindingListView<CalleeInfo> list = mCallees.DataSource as BindingListView<CalleeInfo>;
                CalleeInfo info = list[row.Index].Object;
                if (info != null)
                {
                    foreach (DataGridViewRow item in mMethodList.Rows)
                    {
                        ParsedMethod method = item.DataBoundItem as ParsedMethod;
                        if (Object.ReferenceEquals(method, info.Method))
                        {
                            item.Selected = true;
                            break;
                        }
                    }
                }
            }
        }

        #endregion

        #region -- Private methods --------------------------------------------

        private void OnStopProfiling()
        {
            mProgress.Visible = false;
            UpdateTree();
            OnSaveProject(this, EventArgs.Empty);
        }
        
        private void UpdateTree()
        {
            mProjectTree.BeginUpdate();
            mProjectTree.Nodes.Clear();
            if (mProject != null)
            {
                Text = "Bonehead Performance Profiler - [" + mProject.Name + "]";
                mLaunchButton.Enabled = !mProject.IsProfiling;
                mStopButton.Enabled = mProject.IsProfiling;

                // add project as root.
                TreeNode proj = mProjectTree.Nodes.Add("", mProject.ToString(), 1, 1);
                proj.Tag = mProject;

                // add all profiles.
                foreach (Profile profile in mProject.Profiles)
                {
                    TreeNode node = proj.Nodes.Add("", profile.ToString(), 2, 2);
                    node.Tag = profile;
                }
            }
            else
            {
                Text = "Bonehead Performance Profiler";
                mLaunchButton.Enabled = false;
                mStopButton.Enabled = false;
            }
            mProjectTree.EndUpdate();
            mProjectTree.ExpandAll();
        }

        private bool SaveFirst()
        {
            if (mProject != null)
            {
                DialogResult result = MessageBox.Show(this,
                    "You currently have an open project, do you want to save this before starting a new project?",
                    "Save first?", MessageBoxButtons.YesNoCancel);

                if (result == DialogResult.Yes)
                {
                    OnSaveProject(this, EventArgs.Empty);
                }
                else if (result == DialogResult.Cancel)
                {
                    return false;
                }
            }
            return true;
        }


        private void SelectProfile(ParsedProfile aProfile)
        {
            mThreads.BeginUpdate();
            mThreads.Items.Clear();
            mCallgraph.BeginUpdate();
            mCallgraph.Nodes.Clear();

            foreach (ParsedThread th in aProfile.Threads)
            {
                mThreads.Items.Add(th);
                TreeNode node = mCallgraph.Nodes.Add(th.ToString());
                node.Tag = th;
                AddMethodsToCallgraph(node, th.Root.Children);
                node.Expand();
            }
            
            mCallgraph.EndUpdate();
            mThreads.EndUpdate();
            mThreads.SelectedIndex = 0;
        }


        private void AddMethodsToCallgraph(TreeNode aNode, ICollection<ParsedCallstack> aCalls)
        {
            aNode.Nodes.Clear();
            foreach (ParsedCallstack call in aCalls)
            {
                TreeNode node = aNode.Nodes.Add(call.Method.Name);
                node.Tag = call;
                if (call.Children.Count > 0)
                {
                    node.Nodes.Add("dummy");
                }
            }
        }

        #endregion

        private void mMethodList_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            ParsedMethod method = mPropertyGrid.SelectedObject as ParsedMethod;
            if (method != null)
            {
                foreach (DataGridViewRow item in mMethodList.Rows)
                {
                    ParsedMethod m = item.DataBoundItem as ParsedMethod;
                    if (Object.ReferenceEquals(method, m))
                    {
                        item.Selected = true;
                        break;
                    }
                }
            }
        }


    }
}